package tableaux;

import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import preprocessor.absorption.BinaryAbsorptionGuarded;
import utils.exception.CoreReasoningException;

import entities.concept.*;
import entities.role.Role;
import entities.*;

/**
 * Branch changes can be: 1 conjunction: add labels to current Individual 2
 * disjunction: add labels to current Individual, make branching node, create
 * branches with one OR, set current branch //no action. branches are dealt with
 * by stack of branches, ORs are automatically removed when adding another OR. 3
 * exists: add labels to this Individual, create a successor, add range to
 * successor, relation between ancestor/successor 4 forall: add labels to this
 * Individual and successors 5 axioms: add labels to current Individual
 * !!!!!!Note!!!!!! If c is already present in the label of an ind, c cannot be
 * removed when reversing the changes!
 * 
 * @author j55wu
 * 
 */

public class BranchChanges {

	private static boolean startRecording = false;
	private Map<Individual, HashSet<Concept>> addedLabels;
	private Map<Individual, HashSet<Concept>> rmedLabels;
	private Map<Individual, HashMap<Role, HashSet<Individual>>> addedParentRoleRelations;
	private Map<Individual, HashMap<Role, HashSet<Individual>>> addedChildRoleRelations;
	private Map<Individual, HashMap<Role, HashSet<Individual>>> rmedParentRoleRelations;
	private Map<Individual, HashMap<Role, HashSet<Individual>>> rmedChildRoleRelations;
	private Set<Individual> addedGeneratingParents;
	private Map<Individual, HashSet<Concept>> addedExpandedLabels;
	private Map<Individual, HashSet<Concept>> addedUnfoldedLabels;
	private Map<Individual, Map<AtomicConcept, Set<AtomicConcept>>> addedBinaryPairs;
	private Map<Individual, Map<PConcept, Set<PConcept>>> addedGeneralBinaryPairs;
	private Set<Individual> addedQueueInds;
	private Set<Individual> rmedQueueInds;
	private Set<Individual> GCIapplied;
	private Map<Nominal, Set<Individual>> nominalIndividuals;
	private Map<Nominal, Individual> previousIndividule;
	private Branching branch;

	//
	//
	// public HashSet<Individual> getPotentialQueueInds() {
	// return potentialQueueInds;
	// }
	public void clearNominalInds() {
		this.nominalIndividuals.clear();
	}

	public void clearGCIappliedInds() {
		this.GCIapplied.clear();
	}

	// public void addGCIapplied(Individual n){
	// this.GCIapplied.add(n);
	// }

	// public void recoverGCIappliedInds(){
	// for(Individual n:this.GCIapplied){
	// n.setAppliedGCI(false);
	// }
	// }

	public void clearPotentialQueueInds() {
		this.addedQueueInds.clear();
		this.rmedQueueInds.clear();
	}

	public void recoverQueueInds(List<Individual> q) {
		// first remove the added inds, and re-add the removed ones.
		q.removeAll(addedQueueInds);
		q.addAll(rmedQueueInds);
	}

	public void recoverQueueInds(Set<Individual> q) {
		// first remove the added inds, and re-add the removed ones.
		q.removeAll(addedQueueInds);
		q.addAll(rmedQueueInds);
	}

	// add if op = 1; remove if op = -1;
	public void updateQueueInds(Individual ind, int op) {
		switch (op) {
		case 1:
			this.addedQueueInds.add(ind);
			break;
		case -1:
			this.rmedQueueInds.add(ind);
			break;
		default:
			break;
		}

	}

	public void clearAddedLabels() {
		this.addedLabels.clear();
		rmedLabels.clear();
		previousIndividule.clear();
		this.addedBinaryPairs.clear();
		this.addedGeneralBinaryPairs.clear();
		addedUnfoldedLabels.clear();
	}

	public void clearAllRoleRelations() {
		this.addedParentRoleRelations.clear();
		this.addedChildRoleRelations.clear();
		this.rmedChildRoleRelations.clear();
		this.rmedParentRoleRelations.clear();
		this.addedGeneratingParents.clear();
	}

	// save the interpretation of a nominal before applying exists rule
	public void setPreviousIndividual(Nominal o, Individual n) {
		if (previousIndividule.containsKey(o)
				&& !previousIndividule.get(o).equals(n)) {
			System.out
					.println("You probably need a stack for restoring nominal intps.");
		}
		previousIndividule.put(o, n);
	}

	public static void setRecordingStatus(boolean t) {
		startRecording = t;
	}

	public static boolean getRecordingStatus() {
		return startRecording;
	}

	public Branching getAssociatedBranch() {
		return this.branch;
	}

	public BranchChanges(Branching br) {
		super();
		this.branch = br;
		this.addedLabels = new HashMap<Individual, HashSet<Concept>>();
		rmedLabels = new HashMap<Individual, HashSet<Concept>>();
		previousIndividule = new HashMap<Nominal, Individual>();
		this.addedParentRoleRelations = new HashMap<Individual, HashMap<Role, HashSet<Individual>>>();
		this.addedChildRoleRelations = new HashMap<Individual, HashMap<Role, HashSet<Individual>>>();
		this.rmedChildRoleRelations = new HashMap<Individual, HashMap<Role, HashSet<Individual>>>();
		this.rmedParentRoleRelations = new HashMap<Individual, HashMap<Role, HashSet<Individual>>>();
		this.addedGeneratingParents = new HashSet<Individual>();
		this.addedExpandedLabels = new HashMap<Individual, HashSet<Concept>>();
		this.addedUnfoldedLabels = new HashMap<Individual, HashSet<Concept>>();
		this.addedQueueInds = new HashSet<Individual>();
		this.rmedQueueInds = new HashSet<Individual>();
		this.GCIapplied = new HashSet<Individual>();
		this.nominalIndividuals = new HashMap<Nominal, Set<Individual>>();
		this.addedBinaryPairs = new HashMap<Individual, Map<AtomicConcept, Set<AtomicConcept>>>();
		this.addedGeneralBinaryPairs = new HashMap<Individual, Map<PConcept, Set<PConcept>>>();
	}

	public void rmNominalInds(Nominal o, Individual n) {
		if (!this.nominalIndividuals.containsKey(o)) {
			Set<Individual> ns = new HashSet<Individual>();
			ns.add(n);
			this.nominalIndividuals.put(o, ns);
		} else {
			this.nominalIndividuals.get(o).add(n);
		}
	}

	public void addToExpandedLabel(Individual n, Concept c) {
		if (!addedExpandedLabels.containsKey(n)) {
			HashSet<Concept> as = new HashSet<Concept>();
			as.add(c);
			addedExpandedLabels.put(n, as);
		} else {
			addedExpandedLabels.get(n).add(c);
		}
	}

	public void addToBinaryFired(Individual n, AtomicConcept k1,
			AtomicConcept k2) throws CoreReasoningException {
		Nominal o = null;
		AtomicConcept ac = null;
		if (k1 instanceof Nominal) {
			o = (Nominal) k1;
			ac = k2;
		} else if (k2 instanceof Nominal) {
			o = (Nominal) k2;
			ac = k1;
		} else if (Completion.partiallyGuardedReasoning) {
			throw new CoreReasoningException(
					"Must have one nominal in the keys!");
		}
		if (BinaryAbsorptionGuarded.defOrder
				.equals(BinaryAbsorptionGuarded.entryOrder.Nominal_First)) {
			this.addToBinaryFiredEntry(n, o, ac);
			return;
		}
		if (BinaryAbsorptionGuarded.defOrder
				.equals(BinaryAbsorptionGuarded.entryOrder.Nominal_Second)) {
			this.addToBinaryFiredEntry(n, ac, o);
			return;
		}
	}

	public void addToGeneralBinaryFiredEntry(Individual n, PConcept defr,
			PConcept ac) {
		Map<PConcept, Set<PConcept>> pairs = null;
		Set<PConcept> vs = null;
		if (this.addedGeneralBinaryPairs.containsKey(n)) {
			pairs = this.addedGeneralBinaryPairs.get(n);
		} else {
			pairs = new HashMap<PConcept, Set<PConcept>>();
		}
		if (pairs.containsKey(defr)) {
			vs = pairs.get(defr);
		} else {
			vs = new HashSet<PConcept>();
		}
		vs.add(ac);
		pairs.put(defr, vs);
		this.addedGeneralBinaryPairs.put(n, pairs);
	}
	
	private void addToBinaryFiredEntry(Individual n, AtomicConcept k1,
			AtomicConcept k2) {
		Map<AtomicConcept, Set<AtomicConcept>> pairs = null;
		Set<AtomicConcept> vs = null;
		if (this.addedBinaryPairs.containsKey(n)) {
			pairs = this.addedBinaryPairs.get(n);
		} else {
			pairs = new HashMap<AtomicConcept, Set<AtomicConcept>>();
		}
		if (pairs.containsKey(k1)) {
			vs = pairs.get(k1);
		} else {
			vs = new HashSet<AtomicConcept>();
		}
		vs.add(k2);
		pairs.put(k1, vs);
		this.addedBinaryPairs.put(n, pairs);
	}

	public void addToUnfoldedLabels(Individual n, Concept c) {
		if (!addedUnfoldedLabels.containsKey(n)) {
			HashSet<Concept> as = new HashSet<Concept>();
			as.add(c);
			addedUnfoldedLabels.put(n, as);
		} else {
			// if(! n.getUnfolededLabels().contains(c)){
			addedUnfoldedLabels.get(n).add(c);
			// }
		}
	}

	public void addToLabel(Individual nn, Concept c) {
		if (!addedLabels.containsKey(nn)) {
			HashSet<Concept> as = new HashSet<Concept>();
			as.add(c);
			addedLabels.put(nn, as);
		} else {
			// if(! nn.getLabels().contains(c)){
			addedLabels.get(nn).add(c);
			// }
		}
	}

	public void rmFromLabel(Individual nn, HashSet<Concept> cs) {
		this.rmedLabels.put(nn, cs);
	}

	public void addToLabel(Individual nn, Set<Concept> cs) {
		for (Concept c : cs) {
			this.addToLabel(nn, c);
		}
	}

	public void addGeneratingParent(Individual n) {
		this.addedGeneratingParents.add(n);
	}

	public static enum modType {
		ADD_PARENT, ADD_CHILD, RM_PARENT, RM_CHILD
	}

	public void updateRoleRelations(Individual a, Role r, Individual b,
			modType type) {
		switch (type) {
		case ADD_PARENT:
			this.updateRoleRelations(a, r, b, addedParentRoleRelations);
			break;
		case ADD_CHILD:
			this.updateRoleRelations(a, r, b, addedChildRoleRelations);
			break;
		case RM_PARENT:
			this.updateRoleRelations(a, r, b, rmedParentRoleRelations);
			break;
		case RM_CHILD:
			this.updateRoleRelations(a, r, b, rmedChildRoleRelations);
			break;
		default:
			break;
		}

	}

	// add m - R -> n to appropriate structures
	private void updateRoleRelations(Individual a, Role r, Individual b,
			Map<Individual, HashMap<Role, HashSet<Individual>>> rel) {
		if (rel.containsKey(a)) {
			if (rel.get(a).containsKey(r)) {
				rel.get(a).get(r).add(b);
			} else {
				HashSet<Individual> obs = new HashSet<Individual>();
				obs.add(b);
				HashMap<Role, HashSet<Individual>> newmap = rel.get(a);
				newmap.put(r, obs);
			}
		} else {
			HashSet<Individual> obs = new HashSet<Individual>();
			obs.add(b);
			HashMap<Role, HashSet<Individual>> newmap = new HashMap<Role, HashSet<Individual>>();
			newmap.put(r, obs);
			rel.put(a, newmap);
		}
	}

	public void recoverRelations() {
		// reset all generating parents
		for (Individual n : this.addedGeneratingParents) {
			n.resetGeneratingParent();
		}
		// remove all parent info: a parentOf b
		for (Individual n : addedParentRoleRelations.keySet()) {
			HashMap<Role, HashSet<Individual>> newmap = addedParentRoleRelations
					.get(n);
			for (Entry<Role, HashSet<Individual>> e : newmap.entrySet()) {
				Role r = e.getKey();
				HashSet<Individual> obs = newmap.get(r);
				// n.removeChild(r, obs);
				n.removeParents(r, obs);
			}
		}
		// remove all child info: a childOf b
		for (Individual n : addedChildRoleRelations.keySet()) {
			HashMap<Role, HashSet<Individual>> newmap = addedChildRoleRelations
					.get(n);
			for (Entry<Role, HashSet<Individual>> e : newmap.entrySet()) {
				Role r = e.getKey();
				HashSet<Individual> obs = newmap.get(r);
				// n.removeParents(r, obs);
				n.removeChild(r, obs);
			}
		}
		// add all parents or children
		for (Individual n : rmedParentRoleRelations.keySet()) {
			HashMap<Role, HashSet<Individual>> newmap = rmedParentRoleRelations
					.get(n);
			for (Entry<Role, HashSet<Individual>> e : newmap.entrySet()) {
				Role r = e.getKey();
				HashSet<Individual> obs = newmap.get(r);
				n.addParents(r, obs);
			}
		}
		for (Individual n : rmedChildRoleRelations.keySet()) {
			HashMap<Role, HashSet<Individual>> newmap = rmedChildRoleRelations
					.get(n);
			for (Entry<Role, HashSet<Individual>> e : newmap.entrySet()) {
				Role r = e.getKey();
				HashSet<Individual> obs = newmap.get(r);
				n.addChild(r, obs);
			}
		}
	}

	public void recoverNominalInds() {
		for (Nominal o : this.nominalIndividuals.keySet()) {
			for (Individual n : this.nominalIndividuals.get(o)) {
				o.addAttachedIndividual(n);
			}
		}
	}

	public void recoverLabel() {
		if (previousIndividule.size() > 0) {
			for (Nominal o : previousIndividule.keySet()) {
				Individual n = previousIndividule.get(o);
				o.intepretNominal(n);
				System.out.println("changed back :" + o + "->" + n);
			}
		}
		if (addedLabels.size() > 0) {
			for (Individual n : addedLabels.keySet()) {
				// to be hacked. if n is a nominal node:
				// Def_a and {a} should be re-added
				// so that (when backtracking) this name is always kept
				// Set<Concept> readd = new HashSet<Concept>();
				// if(n.isNominalNode()){
				// for(Nominal nom:n.getNominals()){
				// readd.add(nom);
				// readd.add(PConcept.newGuardPConcept(nom));
				// }
				// }
				// above added on May 14th
				// for(Concept c:addedLabels.get(n)){
				// n.removeLabels(c);
				// Branching depbr = DependencyImp.getDepSetOfConcept(n, c);
				// }
				n.removeLabels(addedLabels.get(n));
				// below added on May 14th
				// for(Concept c:readd){
				// n.addLabels(c);
				// }
			}

		}

		if (this.rmedLabels.size() > 0) {
			for (Individual n : rmedLabels.keySet()) {
				for (Concept c : rmedLabels.get(n)) {
					n.addLabels(c);
				}
			}
		}
		if (addedExpandedLabels.size() > 0) {
			for (Individual n : addedExpandedLabels.keySet()) {
				n.getExpandedLabels().removeAll(addedExpandedLabels.get(n));
				// OrConcept oc = branch.getOriginalDisjunction();
				// if(n.equals(branch.getBranchInd()) &&
				// addedExpandedLabels.get(n).contains(oc)){
				// //don't remove the disjunction just explored: still expanded
				// n.getExpandedLabels().add(oc);
				// }
			}
		}
		if (addedUnfoldedLabels.size() > 0) {
			for (Individual n : addedUnfoldedLabels.keySet()) {
				n.getUnfolededLabels().removeAll(addedUnfoldedLabels.get(n));
			}
		}
		// recover binary pairs
		if (this.addedBinaryPairs.size() > 0) {
			for (Individual n : addedBinaryPairs.keySet()) {
				for (AtomicConcept k1 : addedBinaryPairs.get(n).keySet()) {
					for (AtomicConcept k2 : addedBinaryPairs.get(n).get(k1)) {
						n.removeBinaryEntry(k1, k2);
					}
				}
			}
		}
		if (this.addedGeneralBinaryPairs.size() > 0) {
			for (Individual n : addedGeneralBinaryPairs.keySet()) {
				for (PConcept k1 : addedGeneralBinaryPairs.get(n).keySet()) {
					for (PConcept k2 : addedGeneralBinaryPairs.get(n).get(k1)) {
						n.removeGeneralBinaryEntry(k1, k2);
					}
				}
			}
		}
		// end
	}

}
